package sqba.jamper.map.object;


import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.AbstractAction;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;

import sqba.jamper.ObjectPanel;
import sqba.jamper.map.ILinkable;
import sqba.jamper.map.Map;
//import sqba.jamper.map.object.DragableMapObject.LinkPoint;
import sqba.jamper.util.PathList;
import sqba.jamper.util.XMLMapperInit;
import sqba.jamper.util.XPathUtils;

import javax.swing.JFrame;
//import javax.swing.JPanel;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

//import java.awt.GridLayout;
import java.awt.BorderLayout;
import java.util.Iterator;
import java.awt.Dimension;
import javax.swing.JScrollPane;

/**
 * Combination of several simpler objects
 * representable by a separate map.
 *
 * @author	Filip Pavlovic
 * @version	1.0
 */
public class ExpressionBox extends DragableMapObject
{
	private Map			_map;
	private Designer	_designer;
	private int			_viewWidth, _viewHeight;
	private boolean		_bTemplate;
	private Output		_output;

    private class MyMenu extends JMenu
    {
    	public MyMenu()
    	{
    		super("Expression");

			JMenuItem menuItem;

			menuItem = new JMenuItem("Design...");
	        menuItem.addActionListener(new AbstractAction() {
		        public void actionPerformed(ActionEvent e) {
		        	ExpressionBox.this.design();
		        }
		    });
			this.add(menuItem);

			menuItem = new JMenuItem("Rename...");
	        menuItem.addActionListener(new AbstractAction() {
		        public void actionPerformed(ActionEvent e) {
					String name = JOptionPane.showInputDialog("Enter new name", _name);
					ExpressionBox.this.setName(name);
		        }
		    });
			this.add(menuItem);

			menuItem = new JMenuItem("Add input");
	        menuItem.addActionListener(new AbstractAction() {
		        public void actionPerformed(ActionEvent e) {
		        	int count = ExpressionBox.this.getInputCount();
		        	LinkPoint input = new LinkPoint("Input" + count);
		        	ExpressionBox.this.addInput(input, true);
		        }
		    });
			this.add(menuItem);

			JCheckBoxMenuItem cbMenuItem;

	        cbMenuItem = new JCheckBoxMenuItem("Template");
	        cbMenuItem.addActionListener(new AbstractAction() {
		        public void actionPerformed(ActionEvent e) {
		        	ExpressionBox.this._bTemplate = (((JCheckBoxMenuItem)e.getSource()).isSelected());
		        }
		    });
		    cbMenuItem.setSelected(ExpressionBox.this._bTemplate);
	        this.add(cbMenuItem);
    	}
	}

	private class Designer extends JFrame
	{
		private ObjectPanel _objectPanel = null;
		
		public Designer()
		{
			setIconImage(XMLMapperInit.loadIcon(XMLMapperInit.ICON_APP).getImage());

			_objectPanel = new ObjectPanel(ExpressionBox.this._map);

			ExpressionBox.this._map.setPanel(_objectPanel);

			_objectPanel.setPreferredSize(new Dimension(640, 480));
			JScrollPane scroller = new JScrollPane(_objectPanel);
	        getContentPane().add(scroller, BorderLayout.CENTER);

	        addWindowListener(new WindowAdapter() {
	            public void windowClosing(WindowEvent e) {
	            	Dimension d = _designer.getSize();
	            	ExpressionBox.this._viewWidth = d.width;
	            	ExpressionBox.this._viewHeight = d.height;
	            }
	        });
	        
	        JMenu menu = _objectPanel.getMenu();
			JMenuItem menuItem = new JMenuItem("Add input");
	        menuItem.addActionListener(new AbstractAction() {
		        public void actionPerformed(ActionEvent e) {
		        	int count = ExpressionBox.this.getInputCount();
		        	LinkPoint input = new LinkPoint("Input" + count);
		        	ExpressionBox.this.addInput(input, true);
				}
		    });
	        menu.add(menuItem);
	        //_objectPanel.setMenu(menu);
		}
	}

    /**
     * Expression input
     * If the expression is a template then the output
     * will be a expression parameter, otherwise it
     * represents the source object.
     *
     * @author	Filip Pavlovic
     * @version	1.0
     */
    public class Input extends DragableMapObject
    {
    	private LinkPoint _input;
    	
        private class InputMenu extends JMenu
        {
        	public InputMenu()
        	{
        		super("Input parameter");

    			JMenuItem menuItem;

    			menuItem = new JMenuItem("Rename...");
    	        menuItem.addActionListener(new AbstractAction() {
    		        public void actionPerformed(ActionEvent e) {
    					String name = JOptionPane.showInputDialog("Enter new name", _input.getName());
    					_input.setName(name);
    					_mapObjView.repaint();
    		        }
    		    });
    			this.add(menuItem);
        	}
    	}

        public Input(LinkPoint input, Point pt)
    	{
    		super(ExpressionBox.this._map, input.getName(), pt);
    		_input = input;
    		_linkPoints.add(new LinkPoint(true));
    		setMenu(new InputMenu());
    	}
    	public String getName()			{ return _input.getName(); }
    	public String getUniqueName()	{ return getName(); }
    	public String getXPath()
		{
    		if(!ExpressionBox.this._bTemplate)
    			return getSource().getXPath();
    		else // Template parameter
    			return "$" + _input.getName();
		}
    	public ILinkable getSource()	{ return _input.getSource(); }
    	
    	public Element render(PathList context, Element parent)
    	{
    		return _input.getParentObject().render(context, parent);
    	}
    	
    	public IMapObject getParentObject() { return ExpressionBox.this; }
   }

    /**
     * Expression output
     * A virtual object representing expression output.
     * It is not rendered.
     *
     * @author	Filip Pavlovic
     * @version	1.0
     */
    private class Output extends DragableMapObject
    {
    	public Output(LinkPoint input, Point pt)
    	{
    		super(ExpressionBox.this._map, input.getName(), pt);
    	
    		// Remove output link
    		Iterator iter = _linkPoints.iterator();
    		while ( iter.hasNext() ) {
    			LinkPoint link = (LinkPoint)iter.next();
    			if(link.isOutput()) {
    				_linkPoints.remove(link);
    				break;
    			}
    		}
    		
    		_linkPoints.add(new LinkPoint("input"));
    	}
    	
    	public String getUniqueName()	{ return getName(); }
   }

    /**
     * Default constructor
	 * 
	 * @param map parent map
	 * @param pt object's coordinates in the parent map
     */
	public ExpressionBox(Map map, Point pt)
	{
		super(map, "Expression", pt);
		_viewWidth	= _viewHeight = 200;
		_bTemplate	= true;
		_map		= new Map();
		_designer	= new Designer();
		setMenu( new MyMenu() );
		setOutput( getOutputLink() );
	}
	
    /**
     * Constructor
     * Loads the settings from node parameter.
	 * 
	 * @param map parent map
	 * @param node DOM node containing serialized expression
     */
	public ExpressionBox(Map map, Element node)
	{
		super(map, node);
		setMenu( new MyMenu() );
	}

    /**
     * Returns true if the object is to be rendered as a template.
	 * @return true if the object is to be rendered as a template
     */
	public boolean isTemplate()	{ return _bTemplate; }

    /**
     * Adds an object representing input parameter.
	 * 
	 * @param param
	 * @param bNew flag specifying if the parameter was loaded
	 * and does not need to be added to the inputs collection
     */
	private void addInput(LinkPoint param, boolean bNew)
	{
		if(bNew)
			_linkPoints.add(param);
		
		int count = getInputCount();
		_map.addDragableObject(new Input(param, new Point(10, count*getHeight())));
		
		if(null != _mapObjView)
			_mapObjView.repaint();
	}
	
    /**
     * Opens the design window.
     */
	private void design()
	{
		_designer.setTitle(getName());
		_designer.pack();
		_designer.setSize(_viewWidth, _viewHeight);
		_designer.setVisible(true);
	}

	public Element save(Element node)
	{
		super.save(node);
		_map.save(node);
		
		node.setAttribute("width", String.valueOf(_viewWidth));
		node.setAttribute("height", String.valueOf(_viewHeight));
		node.setAttribute("template", String.valueOf(_bTemplate));
		
		// Save input and output coordinates
		
		return node;
	}
	
	public boolean load(Element node)
	{
		if(super.load(node))
		{
			_map = new Map();
			_designer = new Designer();
			_map.clear();
			loadInputs();
			
			String tmp = XPathUtils.getNodeText(node, "@width/text()");
			int i = tmp != "" ? Integer.valueOf(tmp).intValue() : -1;
			if(i > 0)
				_viewWidth = i;
			
			tmp = XPathUtils.getNodeText(node, "@height/text()");
			i = tmp != "" ? Integer.valueOf(tmp).intValue() : -1;
			if(i > 0)
				_viewHeight = i;

			tmp = XPathUtils.getNodeText(node, "@template/text()");
			if(null != tmp)
				_bTemplate = Boolean.valueOf(tmp).booleanValue();
			
			// Load input and output coordinates

			return _map.loadNoClear(node);
		}
		return false;
	}

    /**
     * Removes input parameter.
	 * 
	 * @param obj parameter object to be removed from the map
     */
	public void removeChild(IMapObject obj)
	{
		if(obj instanceof Input) {
			Input in = (Input)obj;
			super.removeChild(in._input);
			_mapObjView.repaint();
		} else if(obj instanceof LinkPoint) {
			Iterator iter = _map.getChildObjects().iterator();
			while ( iter.hasNext() ) {
				IMapObject tmp = (IMapObject)iter.next();
				if(tmp instanceof Input) {
					Input in = (Input)tmp;
					if(in._input == obj) {
						_map.removeChild(in);
						_mapObjView.repaint();
						break;
					}
				}
			}
			super.removeChild(obj);
		}
	}

	private void loadInputs()
	{
		Iterator iter = _linkPoints.iterator();
		while ( iter.hasNext() ) {
			LinkPoint link = (LinkPoint)iter.next();
			if(!link.isOutput())
				addInput(link, false);
			else
				setOutput(link);
		}
	}
	
	private void setOutput(LinkPoint link)
	{
		int x = _viewWidth - getWidth()*4;
		int y = _viewHeight / 2;
		Point pt = new Point(x, y);
		_output = new Output(link, pt);
		_map.addDragableObject(_output);
	}

	public Element renderDeclaration(PathList context, Document doc)
	{
		if( isTemplate() )
			return renderTemplateDecl(context, doc);
		else
			return null;
	}
	
	private Element renderTemplateDecl(PathList context, Document doc)
	{
		Element e = doc.createElement("xsl:template");
		e.setAttribute("name", "user:" + _name);

		LinkPoint lp = (LinkPoint)_output.getChild("input");
		if(null == lp)
			return null;

		ILinkable src = lp.getSource();
		if(null == src)
			return null;
		
		_map.renderGlobalDeclarations(context, e);
		
		Iterator iter = _linkPoints.iterator();
		while ( iter.hasNext() ) {
			LinkPoint link = (LinkPoint)iter.next();
			if(!link.isOutput())
			{
				Element p = doc.createElement("xsl:param");
				p.setAttribute("name", link.getName());
				//e.appendChild(p);
				e.insertBefore(p, e.getFirstChild());
			}
		}

		if(src instanceof LinkPoint)
			src.getParentObject().render(context, e);
		else
			src.render(context, e);
		
		return e;
	}

	private Element renderTemplateCall(PathList context, Element parent)
	{
		Document doc = parent.getOwnerDocument();
		Element e = doc.createElement("xsl:call-template");
		e.setAttribute("name", "user:" + _name);
		
		Iterator iter = _linkPoints.iterator();
		while ( iter.hasNext() ) {
			LinkPoint link = (LinkPoint)iter.next();
			if(!link.isOutput()) {
				ILinkable src = link.getSource();
				if(null != src) {
					Element p = doc.createElement("xsl:with-param");
					p.setAttribute("name", link.getName());
					p.setAttribute("select", context.getRelativePath(src.getXPath()));
					e.appendChild(p);
				}
			}
		}

		parent.appendChild(e);
		return parent;
	}

	public Element render(PathList context, Element parent)
	{
		if( isTemplate() )
			return renderTemplateCall(context, parent);

		LinkPoint lp = (LinkPoint)_output.getChild("input");
		if(null == lp)
			return parent;

		ILinkable src = lp.getSource();
		if(null == src)
			return parent;
		
		_map.renderGlobalDeclarations(context, parent);
		
		Element expElement = null;
		
		if(src instanceof LinkPoint)
			expElement = src.getParentObject().render(context, parent);
		else
			expElement = src.render(context, parent);
		
		return expElement;
	}
}
